package com.csair.irp.job.file.parser.base;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.csair.irp.job.common.spring.SpringContext;
import com.csair.irp.job.file.exception.DataFileException;
import com.csair.irp.job.file.exception.DataOperaterException;
import com.csair.irp.job.file.exception.FileParseException;
import com.csair.irp.job.file.task.*;
import com.csair.irp.job.file.properties.DataFileProperties;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;

/**
 * 文件解析器
 */
public abstract class BaseFileParser {

    protected final Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private DataFileProperties dataFileProperties;

    @Autowired
    protected FileManageService fileManageService;

    /**
     * 临时文件目录
     */
    @Value("${datafile.tmpdir:${java.io.tmpdir}/data-file}")
    private String tmpdir;

    /**
     * 文件ID分隔符
     */
    private static final String FILE_ID_SPERATOR = ",";

    @PostConstruct
    public void init() {
        try {
            FileUtils.forceMkdir(new File(tmpdir));
        } catch (IOException e) {
            log.error("创建临时目录失败tmpdir={}", tmpdir, e);
        }
    }


    /** 任务开始执行时间 */
    public static final String TASK_START_TIME_KEY = "task_start_time";

    /** 任务信息 */
    public static final String TASK_BASE_INFO = "task_info";

    /** 数据文件信息 */
    public static final String TASK_FILEMANAGE_INFO = "task_filemanage_info";

    /**
     * 由FileTask调用
     * 创建并配置默认参数
     */
    public Map<Object,Object> initParams(DispatchLogPO dispatchLog, FileBaseinfoPO taskInfo) {
        Map<Object, Object> params = new HashMap<>(8);
        //记录开始解析文件的时间
        params.put(TASK_START_TIME_KEY, System.currentTimeMillis());

        //记录任务信息
        params.put(TASK_BASE_INFO, taskInfo);

        return params;
    }

    /**
     * 由FileTask调用
     * 根据调度任务生成文件名
     *
     * @param dispatchLog
     * @param params
     * @return null:说明文件生成规则不合理或数据信息有误；正常情况下返回要解析的文件名
     */
    public abstract String generateFileName(DispatchLogPO dispatchLog, Map<Object, Object> params);


    /**
     * 由FileTask调用
     * 功能描述: 控制解析文件公共流程     基于一个调度任务一个文件
     * 1、根据调度日志生成路径，并获取所有文件列表
     * 2、遍历文件列表解析，当所有文件都解析成功时更新调度日志状态
     *
     * 说明：之前频繁出现事务锁超时问题，因数据采集入库流程逆向操作较容易实现，因此不采用事务方案
     *
     */
    public ParseResult parse(String path, String fileName, DispatchLogPO dispatchLog, Map<Object, Object> params) {
        long dispathLogId = dispatchLog.getId();

        log.info("开始解析文件 path={},fileName={}", path, fileName);

        //解析入库流程
        File file = null;
        Serializable fileId = null;

        try {
            //存储解析结果
            ParseResult parseResult = getParseResult(DispatchLogPO.StateEnum.ETL_STATE_FILE_NOT_FOUND);

            //获取数据文件信息
            FileManagePO fileManage = getFileManage(path, fileName, dispatchLog, params);

            //没找到文件信息就
            if (null == fileManage) {
                //更新调度任务状态
                log.error("数据文件信息为空 任务ID={}", dispathLogId);
                return parseResult;
            }

            //读取文件服务器管理实例(动态配置属性)
            FileManager fileManager = SpringContext.getBean(dataFileProperties.getFileManager().getBeanId());
            file = fileManager.download(path, fileName, new File(tmpdir));

            if (file == null || !file.exists() || file.isDirectory()) {
                //更新调度任务状态
                log.error("文件存在问题file={},dispatchLogId={}", file, dispathLogId);
                return parseResult;
            }

            //如果数据文件信息表不存在该记录则入库
            if (null == fileManage.getFileManageId()) {
                fileManageService.save(fileManage);
            }

            fileId = fileManage.getFileManageId();

            //转换文件类型，如：AET解压文件
           File dataFile = converFile(file);

            if (fileManage.getFileState() != FileManagePO.StateEnum.STATE_RUN_SUCCESS.getCode()) {

                //设置数据文件信息
                params.put(TASK_FILEMANAGE_INFO, fileManage);

                //对文件解析前的操作
                doParseBefore(dataFile, fileId, params);

                //对文件解析入库
                doParse(dataFile, fileId, params);

                //更新文件状态
                fileManageService.updateFileStateByID(FileManagePO.StateEnum.STATE_RUN_SUCCESS, (Long) fileId);

                //文件解析成功就留底
                backupSuccessFile(file, path, fileId, fileName);
            }

            //文件解析完成后的操作
            doParseAfter(dispatchLog, parseResult, file, fileId, params);

            log.info("解析文件成功 path={},fileName={},fileId={}", path, fileName, fileId);

            return parseResult;
        } catch (FileParseException e) {
            return handleException(e.getState(), e, fileId, path + "/" + fileName);
        } catch (DataOperaterException e) {
            return handleException(FileManagePO.StateEnum.STATE_ERROR_DATABASE_INSERT, e, fileId, path + "/" + fileName);
        } catch (Exception e) {
            return handleException(FileManagePO.StateEnum.STATE_ERROR_NOT_FOUND, e, fileId, path + "/" + fileName);
        }
    }

    /**
     * 文件转换
     * @param file
     * @return
     * @throws FileParseException
     */
    protected File converFile(File file) throws FileParseException{
        return file;
    }

    /**
     * 开始解析文件之前的处理逻辑
     * 如：初始化
     * @param file
     * @param fileId
     * @param params
     */
    protected void doParseBefore(File file, Serializable fileId, Map<Object, Object> params) throws FileParseException {
    }

    /**
     * 解析文件异常的处理逻辑
     * @param fileManageState
     * @param e
     * @param fileId
     * @param file
     * @return
     */
    private ParseResult handleException(FileManagePO.StateEnum fileManageState, Exception e, Serializable fileId, String file) {
        try {
            log.error("文件解析入库异常 state={},file={},fileId={}", fileManageState, file, fileId, e);
            //更新文件状态
            fileManageService.updateFileStateByID(fileManageState, (Long) fileId);

            //清除已入库数据
            clearDataByFileId(fileId);
        } catch (Exception e1) {
            log.error("执行解析入库后的操作异常filePath={},fileId={}", file, fileId, e1);
        }
        return getParseResult(DispatchLogPO.StateEnum.ETL_STATE_RUN_FAILURE);
    }

    /**
     * 备份解析成功的文件
     *
     * @param file
     * @param path
     * @param fileId
     * @param fileName
     */
    private void backupSuccessFile(File file, String path, Serializable fileId, String fileName) {
        String backupPath = dataFileProperties.getBackupdir() + File.separator + path;
        try {
            File backupDir = new File(backupPath);
            if (!backupDir.exists() || backupDir.isFile()) {
                FileUtils.forceMkdir(backupDir);
            }
            FileUtils.moveFile(file, new File(backupDir, fileId.toString() + "-" + fileName));
        } catch (IOException e) {
            log.error("文件留底失败file={},backupPath={},fileId={},fileName={}", file, backupPath, fileId, fileName, e);
        }
    }

    /**
     * 获取文件信息，默认从数据库获取数据文件信息(对应手动上传的文件)，
     * 自动(从ftp扫描的文件)应先从数据库获取，如果为null，再手动创建对象
     *
     * @param path
     * @param fileName
     * @param params
     * @return 数据文件信息
     * @throws Exception
     */
    protected FileManagePO getFileManage(String path, String fileName, DispatchLogPO dispatchLog, Map<Object, Object> params) throws DataFileException {
        QueryWrapper<FileManagePO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("link", path).eq("file_name", fileName);
        return fileManageService.getOne(queryWrapper);
    }

    /**
     * 解析数据文件
     *
     * @param file   数据文件
     * @param params
     * @throws Exception
     */
    protected abstract void doParse(File file, Serializable fileId, Map<Object, Object> params) throws Exception;


    /**
     * 按文件Id删除某一文件的入库数据
     *
     * @param fileId 数据文件ID
     * @return 返回操作记录数
     * @throws DataOperaterException
     */
    protected abstract boolean clearDataByFileId(Serializable fileId) throws DataFileException;

    /**
     * 解析文件成功后调用的方法
     * <p>
     * 默认实现：更新调度任务状态、解析入库文件ID、解析时长等
     *
     * @param dispatchLog 调度任务
     * @param parseResult
     * @param file        数据文件
     * @param fileId      数据文件ID
     * @param params      解析过程传递的参数
     * @throws Exception
     */
    protected void doParseAfter(DispatchLogPO dispatchLog, ParseResult parseResult, File file, Serializable fileId, Map<Object, Object> params) throws DataFileException {
        parseResult.setStatus(DispatchLogPO.StateEnum.ETL_STATE_RUN_SUCCESS);
    }

    /**
     * 生成调度任务关联的文件ID
     *
     * @param fileId
     * @param srcId
     * @return
     */
    protected String getResourceId(Serializable fileId, String srcId) {
        Set<String> fileIds = new HashSet<>();
        fileIds.add(fileId.toString());
        //获取解析入库的文件id
        if (StringUtils.isNotBlank(srcId)) {
            for (String tmp : StringUtils.split(srcId, FILE_ID_SPERATOR)) {
                fileIds.add(tmp);
            }
        }
        return StringUtils.join(fileIds, FILE_ID_SPERATOR);
    }

    /**
     * 获取任务执行结果
     *
     * @return
     */
    private ParseResult getParseResult(DispatchLogPO.StateEnum status) {
        return new ParseResult(status);
    }


    protected String getFileInputStreamReaderCode(){
        return "ISO8859-1";
    }

}
